import { $set } from '@feng3d/serialization';
import { BufferGeometry3D, Camera3D, CameraHelper3D, Float32BufferAttribute3D, MathUtils3D, Mesh3D, MeshBasicMaterial3D, Node3D, OrthographicCamera3D, PerspectiveCamera3D, PointsMaterial3D, Scene3D, SphereGeometry3D, StatsComponent, WebGLRenderer3D } from 'feng3d-three';

let SCREEN_WIDTH = window.innerWidth;
let SCREEN_HEIGHT = window.innerHeight;
let aspect = SCREEN_WIDTH / SCREEN_HEIGHT;

let container: HTMLDivElement;
let stats: StatsComponent;
let camera: PerspectiveCamera3D;
let scene: Scene3D;
let renderer: WebGLRenderer3D;
let mesh: Mesh3D;
let cameraRig: Node3D;
let activeCamera: Camera3D;
let activeHelper: CameraHelper3D;
let cameraPerspective: PerspectiveCamera3D;
let cameraOrtho: OrthographicCamera3D;
let cameraPerspectiveHelper: CameraHelper3D;
let cameraOrthoHelper: CameraHelper3D;
const frustumSize = 600;

init();
animate();

function init()
{
    container = document.createElement('div');
    document.body.appendChild(container);

    scene = new Node3D().addComponent('Scene3D');

    //

    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 50, aspect: 0.5 * aspect, near: 1, far: 10000 });
    camera.entity.position.z = 2500;

    cameraPerspective = new Node3D().addComponent('PerspectiveCamera3D', { fov: 50, aspect: 0.5 * aspect, near: 150, far: 1000 });

    cameraPerspectiveHelper = new Node3D().addComponent('CameraHelper3D', { camera: cameraPerspective });
    scene.entity.addChild(cameraPerspectiveHelper.entity);

    //
    cameraOrtho = new Node3D().addComponent('OrthographicCamera3D', { left: 0.5 * frustumSize * aspect / -2, right: 0.5 * frustumSize * aspect / 2, top: frustumSize / 2, bottom: frustumSize / -2, near: 150, far: 1000 });

    cameraOrthoHelper = new Node3D().addComponent('CameraHelper3D', { camera: cameraOrtho });
    scene.entity.addChild(cameraOrthoHelper.entity);

    //

    activeCamera = cameraPerspective;
    activeHelper = cameraPerspectiveHelper;

    // counteract different front orientation of cameras vs rig

    cameraOrtho.entity.rotation.y = Math.PI;
    cameraPerspective.entity.rotation.y = Math.PI;

    cameraRig = new Node3D();

    cameraRig.addChild(cameraPerspective.entity);
    cameraRig.addChild(cameraOrtho.entity);

    scene.entity.addChild(cameraRig);

    //

    mesh = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new SphereGeometry3D(), { radius: 100, widthSegments: 16, heightSegments: 8 }),
        material: $set(new MeshBasicMaterial3D(), { color: { value: 0xffffff }, wireframe: true }),
    });
    scene.entity.addChild(mesh.entity);

    const mesh2 = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new SphereGeometry3D(), { radius: 50, widthSegments: 16, heightSegments: 8 }),
        material: $set(new MeshBasicMaterial3D(), { color: { value: 0x00ff00 }, wireframe: true }),
    });
    mesh2.entity.position.y = 150;
    mesh.entity.addChild(mesh2.entity);

    const mesh3 = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new SphereGeometry3D(), { radius: 5, widthSegments: 16, heightSegments: 8 }),
        material: $set(new MeshBasicMaterial3D(), { color: { value: 0x0000ff }, wireframe: true })
    });
    mesh3.entity.position.z = 150;
    cameraRig.addChild(mesh3.entity);

    //

    const geometry = new BufferGeometry3D();
    const vertices: number[] = [];

    for (let i = 0; i < 10000; i++)
    {
        vertices.push(MathUtils3D.randFloatSpread(2000)); // x
        vertices.push(MathUtils3D.randFloatSpread(2000)); // y
        vertices.push(MathUtils3D.randFloatSpread(2000)); // z
    }

    geometry.setAttribute('position', new Float32BufferAttribute3D(vertices, 3));

    const particles = new Node3D().addComponent('Points3D', {
        geometry,
        material: $set(new PointsMaterial3D(), { color: { value: 0x888888 } }),
    });
    scene.entity.addChild(particles.entity);

    //

    renderer = scene.addComponent('WebGLRenderer3D', { parameters: { antialias: true } });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
    container.appendChild(renderer.domElement);

    renderer.autoClear = false;

    //

    stats = scene.addComponent('StatsComponent');
    container.appendChild(stats.dom);

    //

    window.addEventListener('resize', onWindowResize);
    document.addEventListener('keydown', onKeyDown);
}

//

function onKeyDown(event)
{
    switch (event.keyCode)
    {
        case 79: /* O*/

            activeCamera = cameraOrtho;
            activeHelper = cameraOrthoHelper;

            break;

        case 80: /* P*/

            activeCamera = cameraPerspective;
            activeHelper = cameraPerspectiveHelper;

            break;
    }
}

//

function onWindowResize()
{
    SCREEN_WIDTH = window.innerWidth;
    SCREEN_HEIGHT = window.innerHeight;
    aspect = SCREEN_WIDTH / SCREEN_HEIGHT;

    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);

    camera.aspect = 0.5 * aspect;
    camera.updateProjectionMatrix();

    cameraPerspective.aspect = 0.5 * aspect;
    cameraPerspective.updateProjectionMatrix();

    cameraOrtho.left = -0.5 * frustumSize * aspect / 2;
    cameraOrtho.right = 0.5 * frustumSize * aspect / 2;
    cameraOrtho.top = frustumSize / 2;
    cameraOrtho.bottom = -frustumSize / 2;
    cameraOrtho.updateProjectionMatrix();
}

//

function animate()
{
    requestAnimationFrame(animate);

    render();
    stats.update();
}

function render()
{
    const r = Date.now() * 0.0005;

    mesh.entity.position.x = 700 * Math.cos(r);
    mesh.entity.position.z = 700 * Math.sin(r);
    mesh.entity.position.y = 700 * Math.sin(r);

    mesh.entity.children[0].position.x = 70 * Math.cos(2 * r);
    mesh.entity.children[0].position.z = 70 * Math.sin(r);

    if (activeCamera === cameraPerspective)
    {
        cameraPerspective.fov = 35 + 30 * Math.sin(0.5 * r);
        cameraPerspective.far = mesh.entity.position.length();
        cameraPerspective.updateProjectionMatrix();

        cameraPerspectiveHelper.update();
        cameraPerspectiveHelper.entity.visible = true;

        cameraOrthoHelper.entity.visible = false;
    }
    else
    {
        cameraOrtho.far = mesh.entity.position.length();
        cameraOrtho.updateProjectionMatrix();

        cameraOrthoHelper.update();
        cameraOrthoHelper.entity.visible = true;

        cameraPerspectiveHelper.entity.visible = false;
    }

    cameraRig.lookAt(mesh.entity.position);

    renderer.clear();

    activeHelper.entity.visible = false;

    renderer.setViewport(0, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
    renderer.render(scene, activeCamera);

    activeHelper.entity.visible = true;

    renderer.setViewport(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
    renderer.render(scene, camera);
}

